From 59d638a09fc4f72efb47560d103b2adbc00ce30a Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 13 Dec 2016 19:02:12 +0100 Subject: [PATCH] gsk: Add GskRoundedRect It's essentially a port of GtkRoundedBox to graphene. --- docs/reference/gsk/gsk4-sections.txt | 12 ++ gsk/Makefile.am | 3 + gsk/gsk.h | 1 + gsk/gskenums.h | 20 +- gsk/gskroundedrect.c | 298 +++++++++++++++++++++++++++ gsk/gskroundedrect.h | 96 +++++++++ gsk/gskroundedrectprivate.h | 15 ++ 7 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 gsk/gskroundedrect.c create mode 100644 gsk/gskroundedrect.h create mode 100644 gsk/gskroundedrectprivate.h diff --git a/docs/reference/gsk/gsk4-sections.txt b/docs/reference/gsk/gsk4-sections.txt index 57b8657634..5ea9fbd339 100644 --- a/docs/reference/gsk/gsk4-sections.txt +++ b/docs/reference/gsk/gsk4-sections.txt @@ -57,3 +57,15 @@ GskRenderNodeClass gsk_render_node_get_type GSK_TYPE_BLEND_MODE + +
+GskRoundedRect +GskCorner +GskRoundedRect +gsk_rounded_rect_init +gsk_rounded_rect_init_copy +gsk_rounded_rect_init_from_rect +gsk_rounded_rect_normalize +gsk_rounded_rect_offset +gsk_rounded_rect_is_rectilinear +
diff --git a/gsk/Makefile.am b/gsk/Makefile.am index d7c433c7c4..f27cf2f744 100644 --- a/gsk/Makefile.am +++ b/gsk/Makefile.am @@ -47,6 +47,7 @@ gsk_public_source_h = \ gskenums.h \ gskrenderer.h \ gskrendernode.h \ + gskroundedrect.h \ gsktexture.h \ gsktypes.h gsk_private_source_h = \ @@ -60,12 +61,14 @@ gsk_private_source_h = \ gskprofilerprivate.h \ gskrendererprivate.h \ gskrendernodeprivate.h \ + gskroundedrectprivate.h \ gskshaderbuilderprivate.h \ gsktextureprivate.h gsk_public_source_c = \ gskrenderer.c \ gskrendernode.c \ gskrendernodeimpl.c \ + gskroundedrect.c \ gsktexture.c gsk_private_source_c = \ $(gsk_private_vulkan_source_c) \ diff --git a/gsk/gsk.h b/gsk/gsk.h index 92ce028784..e330600a06 100644 --- a/gsk/gsk.h +++ b/gsk/gsk.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include diff --git a/gsk/gskenums.h b/gsk/gskenums.h index 7b37715ee6..670a034b60 100644 --- a/gsk/gskenums.h +++ b/gsk/gskenums.h @@ -33,6 +33,7 @@ * matrix transform * @GSK_OPACITY_NODE: A node that changes the opacity of its child * @GSK_CLIP_NODE: A node that clips its child to a rectangular area + * @GSK_ROUNDED_CLIP_NODE: A node that clips its child to a rounded rectangle * * The type of a node determines what the node is rendering. * @@ -46,7 +47,8 @@ typedef enum { GSK_TEXTURE_NODE, GSK_TRANSFORM_NODE, GSK_OPACITY_NODE, - GSK_CLIP_NODE + GSK_CLIP_NODE, + GSK_ROUNDED_CLIP_NODE } GskRenderNodeType; /** @@ -109,4 +111,20 @@ typedef enum { GSK_BLEND_MODE_EXCLUSION } GskBlendMode; +/** + * GskCorner: + * @GSK_CORNER_TOP_LEFT: The top left corner + * @GSK_CORNER_TOP_RIGHT: The top right corner + * @GSK_CORNER_BOTTOM_RIGHT: The bottom right corner + * @GSK_CORNER_BOTTOM_LEFT: The bottom left corner + * + * The corner indices used by #GskRoundedRect. + */ +typedef enum { + GSK_CORNER_TOP_LEFT, + GSK_CORNER_TOP_RIGHT, + GSK_CORNER_BOTTOM_RIGHT, + GSK_CORNER_BOTTOM_LEFT +} GskCorner; + #endif /* __GSK_TYPES_H__ */ diff --git a/gsk/gskroundedrect.c b/gsk/gskroundedrect.c new file mode 100644 index 0000000000..0e4173ad30 --- /dev/null +++ b/gsk/gskroundedrect.c @@ -0,0 +1,298 @@ +/* GSK - The GTK Scene Kit + * + * Copyright 2016 Endless + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +/** + * SECTION:GskRoundedRect + * @Title: GskRoundedRect + * @Short_description: A rounded rectangle + * + * #GskRoundedRect defines a rectangle with rounded corners, as is commonly + * used in drawing. + * + * Operations on a #GskRoundedRect will normalize the rectangle, to + * ensure that the bounds are normalized and that the corner sizes don't exceed + * the size of the rectangle. The algorithm used for normalizing corner sizes + * is described in [the CSS specification](https://drafts.csswg.org/css-backgrounds-3/#border-radius). + */ + +#include "config.h" + +#include "gskroundedrect.h" + +#include "gskdebugprivate.h" + +#include + +static void +gsk_rounded_rect_normalize_in_place (GskRoundedRect *self) +{ + float factor = 1.0; + float corners; + guint i; + + graphene_rect_normalize (&self->bounds); + + for (i = 0; i < 4; i++) + { + self->corner[i].width = MAX (self->corner[i].width, 0); + self->corner[i].height = MAX (self->corner[i].height, 0); + } + + /* clamp border radius, following CSS specs */ + corners = self->corner[GSK_CORNER_TOP_LEFT].width + self->corner[GSK_CORNER_TOP_RIGHT].width; + if (corners > self->bounds.size.width) + factor = MIN (factor, self->bounds.size.width / corners); + + corners = self->corner[GSK_CORNER_TOP_RIGHT].height + self->corner[GSK_CORNER_BOTTOM_RIGHT].height; + if (corners > self->bounds.size.height) + factor = MIN (factor, self->bounds.size.height / corners); + + corners = self->corner[GSK_CORNER_BOTTOM_RIGHT].width + self->corner[GSK_CORNER_BOTTOM_LEFT].width; + if (corners > self->bounds.size.width) + factor = MIN (factor, self->bounds.size.width / corners); + + corners = self->corner[GSK_CORNER_TOP_LEFT].height + self->corner[GSK_CORNER_BOTTOM_LEFT].height; + if (corners > self->bounds.size.height) + factor = MIN (factor, self->bounds.size.height / corners); + + for (i = 0; i < 4; i++) + graphene_size_scale (&self->corner[i], factor, &self->corner[i]); +} + +/** + * gsk_rounded_rect_init: + * @self: The #GskRoundedRect to initialize + * @bounds: a #graphene_rect_t describing the bounds + * @top_left: the rounding radius of the top left corner + * @top_right: the rounding radius of the top right corner + * @bottom_right: the rounding radius of the bottom right corner + * @bottom_left: the rounding radius of the bottom left corner + * + * Initializes the given #GskRoundedRect with the given values. + * + * This function will implicitly normalize the #GskRoundedRect + * before returning. + * + * Returns: (transfer none): the initialized rectangle + * + * Since: 3.90 + */ +GskRoundedRect * +gsk_rounded_rect_init (GskRoundedRect *self, + const graphene_rect_t *bounds, + const graphene_size_t *top_left, + const graphene_size_t *top_right, + const graphene_size_t *bottom_right, + const graphene_size_t *bottom_left) +{ + graphene_rect_init_from_rect (&self->bounds, bounds); + graphene_size_init_from_size (&self->corner[GSK_CORNER_TOP_LEFT], top_left); + graphene_size_init_from_size (&self->corner[GSK_CORNER_TOP_RIGHT], top_right); + graphene_size_init_from_size (&self->corner[GSK_CORNER_BOTTOM_RIGHT], bottom_right); + graphene_size_init_from_size (&self->corner[GSK_CORNER_BOTTOM_LEFT], bottom_left); + + gsk_rounded_rect_normalize_in_place (self); + + return self; +} + +/** + * gsk_rounded_rect_init_copy: + * @self: a #GskRoundedRect + * @src: a #GskRoundedRect + * + * Initializes @self using the given @src rectangle. + * + * This function will implicitly normalize the #GskRoundedRect + * before returning. + * + * Returns: (transfer none): the initialized rectangle + * + * Since: 3.90 + */ +GskRoundedRect * +gsk_rounded_rect_init_copy (GskRoundedRect *self, + const GskRoundedRect *src) +{ + *self = *src; + + gsk_rounded_rect_normalize_in_place (self); + + return self; +} + +/** + * gsk_rounded_rect_init_from_rect: + * @self: a #GskRoundedRect + * @bounds: a #graphene_rect_t + * @radius: the border radius + * + * Initializes @self to the given @bounds and sets the radius of all + * four corners to @radius. + * + * Returns: (transfer none): the initialized rectangle + **/ +GskRoundedRect * +gsk_rounded_rect_init_from_rect (GskRoundedRect *self, + const graphene_rect_t *bounds, + float radius) +{ + graphene_size_t corner = GRAPHENE_SIZE_INIT(radius, radius); + + return gsk_rounded_rect_init (self, bounds, &corner, &corner, &corner, &corner); +} + +/** + * gsk_rounded_rect_normalize: + * @self: a #GskRoundedRect + * + * Normalizes the passed rectangle. + * + * this function will ensure that the bounds of the rectanlge are normalized + * and ensure that the corner values are positive and the corners do not overlap. + * + * Returns: (transfer none): the normalized rectangle + * + * Since: 3.90 + */ +GskRoundedRect * +gsk_rounded_rect_normalize (GskRoundedRect *self) +{ + gsk_rounded_rect_normalize_in_place (self); + + return self; +} + +/** + * gsk_rounded_rect_offset: + * @self: a #GskRoundedRect + * @d_x: the horizontal offset + * @d_y: the vertical offset + * + * Offsets the bound's origin by @dx and @dy. + * + * The size and corners of the rectangle are unchanged. + * + * Returns: (transfer none): the offset rectangle + * + * Since: 3.90 + */ +GskRoundedRect * +gsk_rounded_rect_offset (GskRoundedRect *self, + float dx, + float dy) +{ + gsk_rounded_rect_normalize (self); + + self->bounds.origin.x += dx; + self->bounds.origin.y += dy; + + return self; +} + +/** + * gsk_rounded_rect_is_rectilinear: + * @self: the #GskRoundedRect to check + * + * Checks if all corners of @self are right angles and the + * rectangle covers all of its bounds. + * + * This information can be used to decide if gsk_clip_node_new() + * or gsk_rounded_clip_node_new() should be called. + * + * Returns: %TRUE if the rectangle is rectilinear + **/ +gboolean +gsk_rounded_rect_is_rectilinear (GskRoundedRect *self) +{ + guint i; + + for (i = 0; i < 4; i++) + { + if (self->corner[i].width > 0 || + self->corner[i].height > 0) + return FALSE; + } + + return TRUE; +} + +static void +append_arc (cairo_t *cr, double angle1, double angle2, gboolean negative) +{ + if (negative) + cairo_arc_negative (cr, 0.0, 0.0, 1.0, angle1, angle2); + else + cairo_arc (cr, 0.0, 0.0, 1.0, angle1, angle2); +} + +static void +_cairo_ellipsis (cairo_t *cr, + double xc, double yc, + double xradius, double yradius, + double angle1, double angle2) +{ + cairo_matrix_t save; + + if (xradius <= 0.0 || yradius <= 0.0) + { + cairo_line_to (cr, xc, yc); + return; + } + + cairo_get_matrix (cr, &save); + cairo_translate (cr, xc, yc); + cairo_scale (cr, xradius, yradius); + append_arc (cr, angle1, angle2, FALSE); + cairo_set_matrix (cr, &save); +} + +void +gsk_rounded_rect_path (const GskRoundedRect *self, + cairo_t *cr) +{ + cairo_new_sub_path (cr); + + _cairo_ellipsis (cr, + self->bounds.origin.x + self->corner[GSK_CORNER_TOP_LEFT].width, + self->bounds.origin.y + self->corner[GSK_CORNER_TOP_LEFT].height, + self->corner[GSK_CORNER_TOP_LEFT].width, + self->corner[GSK_CORNER_TOP_LEFT].height, + G_PI, 3 * G_PI_2); + _cairo_ellipsis (cr, + self->bounds.origin.x + self->bounds.size.width - self->corner[GSK_CORNER_TOP_RIGHT].width, + self->bounds.origin.y + self->corner[GSK_CORNER_TOP_RIGHT].height, + self->corner[GSK_CORNER_TOP_RIGHT].width, + self->corner[GSK_CORNER_TOP_RIGHT].height, + - G_PI_2, 0); + _cairo_ellipsis (cr, + self->bounds.origin.x + self->bounds.size.width - self->corner[GSK_CORNER_BOTTOM_RIGHT].width, + self->bounds.origin.y + self->bounds.size.height - self->corner[GSK_CORNER_BOTTOM_RIGHT].height, + self->corner[GSK_CORNER_BOTTOM_RIGHT].width, + self->corner[GSK_CORNER_BOTTOM_RIGHT].height, + 0, G_PI_2); + _cairo_ellipsis (cr, + self->bounds.origin.x + self->corner[GSK_CORNER_BOTTOM_LEFT].width, + self->bounds.origin.y + self->bounds.size.height - self->corner[GSK_CORNER_BOTTOM_LEFT].height, + self->corner[GSK_CORNER_BOTTOM_LEFT].width, + self->corner[GSK_CORNER_BOTTOM_LEFT].height, + G_PI_2, G_PI); + + cairo_close_path (cr); +} + diff --git a/gsk/gskroundedrect.h b/gsk/gskroundedrect.h new file mode 100644 index 0000000000..96551edd43 --- /dev/null +++ b/gsk/gskroundedrect.h @@ -0,0 +1,96 @@ +/* GSK - The GTK Scene Kit + * + * Copyright 2016 Endless + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GSK_ROUNDED_RECT_H__ +#define __GSK_ROUNDED_RECT_H__ + +#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +/** + * GSK_ROUNDED_RECT_INIT: + * @_x: the X coordinate of the origin + * @_y: the Y coordinate of the origin + * @_w: the width + * @_h: the height + * + * Initializes a #GskRoundedRect when declaring it. + * + * Since: 3.90 + */ +#define GSK_ROUNDED_RECT_INIT(_x,_y,_w,_h) (GskRoundedRect) { .rect = GRAPHENE_RECT_INIT(_x,_y,_w,_h) } + +/** + * GskRoundedRect: + * @bounds: the bounds of the rectangle + * @corner: the size of the 4 rounded corners + * + * A rectanglular region with rounded corners. + * + * Application code should normalize rectangles using gsk_rounded_rect_normalize(); + * this function will ensure that the bounds of the rectanlge are normalized + * and ensure that the corner values are positive and the corners do not overlap. + * All functions taking a #GskRoundedRect as an argument will internally operate on + * a normalized copy; all functions returning a #GskRoundedRect will always return + * a normalized one. + * + * Since: 3.90 + */ +typedef struct _GskRoundedRect GskRoundedRect; + +struct _GskRoundedRect +{ + graphene_rect_t bounds; + + graphene_size_t corner[4]; +}; + +GDK_AVAILABLE_IN_3_90 +GskRoundedRect * gsk_rounded_rect_init (GskRoundedRect *self, + const graphene_rect_t *rect, + const graphene_size_t *top_left, + const graphene_size_t *top_right, + const graphene_size_t *bottom_right, + const graphene_size_t *bottom_left); +GDK_AVAILABLE_IN_3_90 +GskRoundedRect * gsk_rounded_rect_init_copy (GskRoundedRect *self, + const GskRoundedRect *src); +GDK_AVAILABLE_IN_3_90 +GskRoundedRect * gsk_rounded_rect_init_from_rect (GskRoundedRect *self, + const graphene_rect_t *bounds, + float radius); + +GDK_AVAILABLE_IN_3_90 +GskRoundedRect * gsk_rounded_rect_normalize (GskRoundedRect *self); + +GDK_AVAILABLE_IN_3_90 +GskRoundedRect * gsk_rounded_rect_offset (GskRoundedRect *self, + float dx, + float dy); + +GDK_AVAILABLE_IN_3_90 +gboolean gsk_rounded_rect_is_rectilinear (GskRoundedRect *self); + +G_END_DECLS + +#endif /* __GSK_ROUNDED_RECT_H__ */ diff --git a/gsk/gskroundedrectprivate.h b/gsk/gskroundedrectprivate.h new file mode 100644 index 0000000000..39b968c562 --- /dev/null +++ b/gsk/gskroundedrectprivate.h @@ -0,0 +1,15 @@ +#ifndef __GSK_ROUNDED_RECT_PRIVATE_H__ +#define __GSK_ROUNDED_RECT_PRIVATE_H__ + +#include "gskroundedrect.h" + +#include + +G_BEGIN_DECLS + +void gsk_rounded_rect_path (const GskRoundedRect *self, + cairo_t *cr); + +G_END_DECLS + +#endif /* __GSK_ROUNDED_RECT_PRIVATE_H__ */ -- 2.30.2